# Tan
import csv
import json
import signal
from datetime import datetime
import sys, os
import time

# import joblib
import pandas as pd
import serial
import numpy as np
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
import pywt


"""AGX数据"""
# 可处理的原始数据：file.write(data_row_str + "\n")
"""服务器数据"""
# 1、实时计算的：
# 时间戳为键，压力原始数据为值：json.dump(data_dict, json_file)
# 电量：json.dump(DL_dict, json_file)
# 坐姿：json.dump(Posture_dict, json_file)
# 坐骨结节：print('左y,x', left_max[0], left_max[1], '右y,x', right_max[0], right_max[1])

# 2、需调用坐骨结节数据计算的：
# 疲劳特征（用于送入模型检测疲劳的，我不确定这边是否需要所以一并放上来了）：print(f"疲劳特征: {features_2d}")
# 大屏显示小波熵（可以任选一轮显示）：print(node_energy)

"""麻烦师兄帮我把这一段加入到程序结束前，如此能：稳定程序并提升数据质量"""
# 清理工作，下位机初始化+关闭串口
#     ser.write(bytes.fromhex('02'))
#     port_close()


"""串口配置"""
ser = serial.Serial(
    port='/dev/ttyUSB0',  # 串口号
    baudrate=115200,  # 波特率
    bytesize=8,
    stopbits=1,
    parity="N",
    timeout=0.001)
# 由于后续使用read在未收全数据的时候，会按照一个timeout周期时间读取数据

"""数据存储位置"""
c_time = datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
file_path = os.path.join('/media/multimodal/HIKVISION/Data_save/', f'Pad_{c_time}.txt')
file = open(file_path, "a")


"""压力数据初始化"""
hang = 0  # 初始化行数
data = np.zeros((32, 32), dtype=float)  # 使用NumPy数组替代Python列表
error = 0
i = 0
flag_initial = 1
"""时间"""
timer = QTimer()
last_time_recorded = time.time()
time_interval = 600  # s_10分钟
# time_interval = 10  # s_10分钟
flag_time=0
"""基础特征初始化"""
basic_feature = []  # 坐骨结节数据

def sigint_handler(*args):
    print("捕获到Ctrl+C，准备退出...")
    # 清理工作，比如关闭串口、保存数据等
    ser.write(bytes.fromhex('02'))
    port_close()
    sys.exit(0)

# 在启动事件循环前设置信号处理器
signal.signal(signal.SIGINT, sigint_handler)

def port_close():
    # QApplication.quit()
    try:
        ser.close()
        if ser.isOpen():
            print("串口关闭失败！")
        else:
            print("串口关闭成功！")
    except Exception as e:
        print(f"关闭串口时发生错误: {e}")


def port_open():
    ser.open()
    if ser.isOpen():
        print("打开串口成功, 串口号: %s" % ser.name)
    else:
        print("打开串口失败")


def calculate_center_of_mass(data):
    # 创建网格坐标，用于后续加权平均
    x, y = np.meshgrid(np.arange(data.shape[1]), np.arange(data.shape[0]))
    # 计算分子的和，即每个传感器坐标乘以相应的压力值的总和
    numerator_x = np.sum(data * x)
    # 计算分母的和，即所有传感器的压力值的总和
    denominator = np.sum(data)
    # print("压力值和",denominator)
    if denominator != 0:
        center_of_mass_x = int(numerator_x / denominator)
    else:
        center_of_mass_x = 0

    return center_of_mass_x
def split_and_find_max(data, l):
    if l !=0:
        # 分割数据为左右两个区域
        left_region = data[:, :l]  # 包括指定列 l 及其左侧的所有列
        right_region = data[:, l:]  # 包括指定列 l 及其右侧的所有列

        left_max_index = np.unravel_index(np.argmax(left_region), left_region.shape)
        right_max_index = np.unravel_index(np.argmax(right_region), right_region.shape)

        # 转换坐标为全局坐标
        left_max_index_global = (left_max_index[0], left_max_index[1])  # 对于左区域，坐标不变
        right_max_index_global = (right_max_index[0], right_max_index[1] + l)  # 对于右区域，列坐标需要加上 l

        left_all_value = np.sum(left_region)
        right_all_value = np.sum(right_region)
        # print("区域值", left_all_value, right_all_value)
        if 2 * abs(left_all_value - right_all_value) / (left_all_value + right_all_value) < 0.05:  # 中间
            return "N", left_max_index_global, right_max_index_global
        else:
            if left_all_value > right_all_value:
                return "L", left_max_index_global, right_max_index_global
            else:
                return "R", left_max_index_global, right_max_index_global
    else:
        return "N", (0, 0), (0, 0)
def calculate_wavelet_packet_features(data, wavelet='db5', level=3):
    # 进行小波包分解
    wp = pywt.WaveletPacket(data, wavelet, mode='symmetric', maxlevel=level)
    # 初始化各频带节点能量，但这里我们只需要第一个节点的能量
    node_energy = None
    # 遍历每个节点
    for node in wp.get_level(level, 'freq'):
        # 计算节点的能量
        energy = sum(coeff ** 2 for coeff in node.data)
        node_energy = energy  # 存储第一个节点的能量后即可跳出循环
        break  # 计算完第一个节点就停止遍历
    return np.round(node_energy, 2)


"""数据存储"""
def update_heatmap(hang, lie, q_pad):
    global data, error, Dian_liang, basic_feature,loaded_model

    if not error:
        data[hang - 1, :] = np.maximum(0, np.round(lie, 2))  # 因为hang的范围应该是0～31
    else:
        error = 0
        data[hang - 1, :] = data[hang - 2, :]

    data_row_str = f"{hang}H," + ",".join(map(str, data[hang - 1, :]))  # 将data[hang-1, :]数据用逗号分隔并转换为字符串
    file.write(data_row_str + "\n")  # 写入data[hang-1, :]数据到文件，以及一个换行符
    # print("已解析：" + data_row_str + "\n")

    if hang == 32:
        """实时数据（时间、压力、电量、坐骨结节）"""
        center_of_mass_x = calculate_center_of_mass(data)
        posture, left_max, right_max = split_and_find_max(data, center_of_mass_x)
        timestamp = datetime.now().strftime('%Y_%m_%d_%H_%M_%S')  # 获取当前时间戳
        # 将时间戳作为键，数据作为值
        data_dict = {timestamp: data.tolist()}
        # DL为键，电量数字为值
        DL_dict = {'DL': Dian_liang}
        Posture_dict = {'Posture': posture}
        # 将字典写入JSON文件
        with open('/media/multimodal/HIKVISION/Data_save/pad_data/data1.json', 'a') as json_file:
            json.dump(Posture_dict, json_file)
            json.dump(DL_dict, json_file)  # DL:电池电量
            json.dump(data_dict, json_file)  # 时间戳:压力原始数据
        """左右坐骨结节坐标"""
        print('左y,x', left_max[0], left_max[1], '右y,x', right_max[0], right_max[1])
        left_right_axis = [left_max[0].tolist(), left_max[1].tolist(), right_max[0].tolist(), right_max[1].tolist()]

        q_pad.put((data_dict, DL_dict, Posture_dict, left_right_axis, wavelet_feature))
        if q_pad.qsize() > 100:
            q_pad.get()
        # print(f'wavelet_feature: {wavelet_feature}')
        # print(f'坐垫队列大小: {q_pad.qsize()}')

        if flag_time == 1:
            print("开始窗口计算")
            basic_feature.pop(0)  # 或者使用 basic_feature = basic_feature[1:] 来切片删除第一行
            basic_feature.append([left_max[0], left_max[1], right_max[0], right_max[1]])
            print(basic_feature)
            """计算特征"""
            calculate_feature()
        else:
            basic_feature.append([left_max[0], left_max[1], right_max[0], right_max[1]])
            print(basic_feature)


"""定时保存"""


def write_time_to_file():
    current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    with open(file_path, "a") as file:
        file.write("Time: " + current_time + "\n")


def handle_H_data(recv, q_pad):
    global hang, error

    hang = int(recv.split("H")[0])
    Lie_part = recv.split(":")[1]
    lie = [float(x) for x in Lie_part.split(",") if x.strip()]

    if len(lie) < 32 or any(x < 0 or x > 5 for x in lie):
        error = 1
        lie = data[hang - 2, :]
    update_heatmap(hang, lie, q_pad)


def handle_C_data(recv):
    global Dian_liang
    Dian_liang = recv.split(":")[1].strip()
    print("电量：" + Dian_liang + "\n")
    # file.write("电量：" + Dian_liang + "\n")


wavelet_feature = [25, 12, 10, 3, 7, 2, 9, 8]
def calculate_feature():
    global basic_feature, features_2d, wavelet_feature
    # 转换列表为DataFrame
    column_names = ['Left_Y', 'Left_X', 'Right_Y', 'Right_X']
    df = pd.DataFrame(basic_feature, columns=column_names)
    features = []  # 清空列表存储本次疲劳特征
    for column_name in df.columns:
        print(column_name)
        column_data = df[column_name]
        rms = round(np.sqrt(np.mean(column_data ** 2)), 2)
        node_energy = calculate_wavelet_packet_features(column_data)
        # print(f'测试测试：{node_energy}')
        # 将每列的RMS和node_energy添加到列表中
        features.extend([rms, node_energy])
    wavelet_feature = features
    features_2d = [features]  # 将一维列表转换为二维列表
    print(f"疲劳特征: {features_2d}")


def update_data(q_pad):
    global last_time_recorded, recv, i, flag_initial, features_2d, flag_time, loaded_model

    print(ser.inWaiting())
    if ser.inWaiting() > 0:
        flag_initial = 0
        if ser.inWaiting() >= 166:
            recv = ser.readall().decode("utf-8", errors="ignore")
            # print(recv)
            ser.write(bytes.fromhex('03'))  # 上位机处理数据，同时控制下位机采集
            revs = recv.split("\r\n")
            for frame in revs:
                if frame:
                    if frame[2] == 'H':
                        handle_H_data(frame, q_pad)
                    elif frame[0] == 'C':
                        handle_C_data(frame)
        # 初始运行，串口就没数据，则重启串口
        # 一旦有数据，则再也不重启串口
    elif flag_initial == 1:
        if ser.inWaiting() == 0:
            i += 1
            # print("第" + "%d" % i + "次无数据")
            if i == 10:
                i = 0
                port_close()
                port_open()
                ser.write(bytes.fromhex('01'))  # 测电量
                ser.write(bytes.fromhex('03'))  # 开启下位机采集
                print('重新连接')

    current_time = time.time()
    """10分钟"""
    if current_time - last_time_recorded >= time_interval:
        write_time_to_file()  # 写入time标签
        ser.write(bytes.fromhex('01'))  # 测电量
        last_time_recorded = current_time
        print('时间到')
        if flag_time==0: # 第一次计算所有基础特征构建的疲劳特征
            flag_time=1
            """计算特征"""
            calculate_feature()
            print("特征",features_2d)

# if __name__ == '__main__':
def main(q_pad):
    # app2 = QApplication(sys.argv)
    """打开串口"""
    if ser.isOpen():
        print("打开串口成功, 串口号: %s" % ser.name)
    else:
        print("打开串口失败")
    """开始采集"""
    ser.write(bytes.fromhex('01'))  # 测电量
    ser.write(bytes.fromhex('03'))  # 开启下位机采集
    while True:
        try:
            update_data(q_pad)
            time.sleep(1/40)
        except Exception as e:
            print(f'坐垫错误打印: {e}')
            continue
    # timer.timeout.connect(lambda: update_data(q_pad))  # 更新热力图
    # timer.start(25)  #

    # sys.exit(app2.exec())
